import IndexDB, {
  isArray,
  wrapArray,
  isFunction,
  ACCESS_MODE,
  DBKeyRangeOperator,
} from '../idb/uitils'
import IDB from '../idb/idb'

/**
 * 计算分页参数
 * @param {*} paging
 */
function createPagingParams(paging) {
  if (!paging || !paging.pageSize || !paging.pageNum) {
    return null
  }
  return {
    ...paging,
    startIndex: (paging.pageNum - 1) * paging.pageSize,
    endIndex: paging.pageNum * paging.pageSize - 1,
    currentIndex: 0,
  }
}

/**
 * @method 游标开启成功,遍历游标
 * @param {Function} 条件
 * @param {Function} 满足条件的处理方式currentValue当前值
 * @param {Function} 游标遍历完执行的方法
 * @param {Object} 遍历游标时的上下文对象
 * @return {Null}
 * */
export async function iterateCursor(
  cursor,
  { condition = () => true, handler, over, context }
) {
  if (cursor) {
    const currentValue = cursor.value
    const { paging } = context
    if (condition({ currentValue, context })) {
      if (context.getStatus() === null) {
        context.count++
        let doHandler = true
        if (paging) {
          const currentIndex = paging.currentIndex
          paging.currentIndex++
          if (currentIndex < paging.startIndex || currentIndex > paging.endIndex) {
            doHandler = false
            if (currentIndex > paging.endIndex) {
              context.cancel(true)
            }
          }
        }
        if (doHandler) {
          await handler({ cursor, currentValue, context })
        }
      }
    }
    switch (context.getStatus()) {
      case true:
        over && over()
        break
      case false:
        break
      default:
        cursor.continue()
        break
    }
  } else {
    over && over()
  }
}

/**
 * 创建游标遍历时的上下文对象，支持取消遍历、遍历计数
 */
export function createCursorIterateContext(paging) {
  let cancelStatus = null // 取消执行标识（null：继续执行、true：取消执行并调用over、false：取消执行）
  const context = {
    cancel(status) {
      cancelStatus = status
    },
    getStatus() {
      return cancelStatus
    },
    count: 0,
    paging: createPagingParams(paging),
  }
  return context
}

/**
 * IndexDB数据库实体对象基类
 * hainee@163.com 2020-11
 * @param config >>
    name: 'feeds',
    tableName: 'Feed'
    mixnDefaultMethods: null, // 混入指定默认函数（save、remove、clearPropertyCache），如果为null || true，则混入，如果为false，则不混入
    propertyHandler: (data, dbStore, modelConfig) => {} // 直接手动自己编写关联数据查询函数
    foreignKey: 'pageId', // 如果有该参数，则在关联查询时采用该值对应的索引查找
    indexs: [] 按给定索引查询关联数据
    condition: (a, b) => { return true } 如果不采用索引查找，则配置condition回调函数来执行判断是否关联
    如果propertyHandler、foreignKey、condition和indexs均未设置，则默认使用当前实体的keyPath索引查找
    优先级： handler > foreignKey > indexs > condition，三种查询条件只能有一种生效
    cache: false, // 是否开启缓存，如果为true：则该属性只查询一次，否则每次查询
    handler: (d) => {} // 查询结果处理
 * @param {*} dbStore IndexDB数据查询接口：InedxDBStore
 */
export default function IndexDBModel(dbStore, config) {
  this.dbStore = dbStore
  this.db = dbStore.db || new IDB()
  this.config = config

  const invalidKeys = {}
  config.props &&
    config.props.forEach((d) => {
      invalidKeys[d.name] = true
    })
  config.methods &&
    Object.keys(config.methods).forEach((d) => {
      invalidKeys[d] = true
    })
  if (!config.fields) {
    this.unwrapData = function (data) {
      const result = {}
      Object.keys(data).forEach((key) => {
        if (invalidKeys[key]) {
          return
        }
        const val = data[key]
        if (val && isFunction(val)) {
          return
        }
        result[key] = val
      })
      return result
    }
  } else {
    const unwrapFields = config.fields
      .map((d) => {
        return `${d}:data.${d}`
      })
      .join(',')
    // eslint-disable-next-line no-new-func
    this.unwrapData = new Function('data', `return {${unwrapFields}}`)
  }

  /**
   * 保存实体，如果实体的主键已存在，则覆盖
   * @param {*} data 实体数据 Object or Array
   * @param {Boolean} wait 是否等待操作完成
   */
  this.insert = function (data, wait = false) {
    const datas = wrapArray(data).map((d) => {
      return this.unwrapData(d)
    })
    return new Promise((resolve, reject) => {
      const store = this.db.getDBStore(
        this.config.tableName,
        ACCESS_MODE.ReadWrite
      )
      const total = datas.length
      let counter = 0
      let errors = []
      let hasReject = false

      function doReject() {
        if (hasReject) {
          return
        }
        hasReject = true
        const err = new Error(
          `insert更新操作失败！失败数量：${errors}，具体失败对象参见：errorDatas。`
        )
        err.errorDatas = errors
        reject(err)
      }
      try {
        datas.forEach((d) => {
          const req = store.put(d)
          if (wait) {
            req.onsuccess = () => {
              // 每操作成功一个，计数加一
              counter++
              if (counter === total) {
                // 如果计数等于数据总数，则表示所有更新完成
                if (!errors.length) {
                  resolve(true)
                } else {
                  doReject()
                }
              }
            }
            req.onerror = () => {
              counter++
              // 每操作失败一个，计数加一
              errors.push(d)
              if (counter === total) {
                // 如果计数等于数据总数，则表示所有更新完成
                doReject()
              }
            }
          }
        })
        if (!wait) {
          resolve(true)
        }
      } catch (err) {
        reject(err)
      }
    })
  }

  /**
   * @method 删除数据
   * @param {Function} condition 查询的条件，遍历，与filter类似
   * @param {Boolean} wait 是否等待更新操作完成
   * */
  this.delete = function (condition, wait = false) {
    return new Promise(async (resolve, reject) => {
      try {
        const res = []
        let total = 0
        let counter = 0
        let errors = []
        let hasReject = false

        function doReject() {
          if (hasReject) {
            return
          }
          hasReject = true
          const err = new Error(
            `delete操作失败！失败数量：${errors}，具体失败对象参见：errorDatas。`
          )
          err.errorDatas = errors
          reject(err)
        }
        const iterateParam = {
          condition,
          handler: ({ currentValue }) => {
            res.push(currentValue)
            total++
            const req = cursor.delete()
            if (wait) {
              req.onsuccess = () => {
                counter++
                if (counter === total) {
                  if (!errors.length) {
                    resolve(res)
                  } else {
                    doReject()
                  }
                }
              }
              req.onerror = () => {
                counter++
                errors.push(currentValue)
                if (counter === total) {
                  doReject()
                }
              }
            }
          },
          over: () => {
            if (!wait) {
              if (!errors.length) {
                resolve(res)
              } else {
                doReject()
              }
            }
          },
          context: createCursorIterateContext(),
        }
        const cursor = await this.db.openDBStoreCursor({ tableName: this.config.tableName })
        if (cursor) {
          iterateCursor(cursor, iterateParam)  
        }
      } catch (err) {
        reject(err)
      }
    })
  }

  /**
   * @method 删除数据(主键)
   * @param {Object}
   *   @property {String\|Number} target 目标主键值
   *   @property {Function} [success] 删除成功的回调  @return {Null}
   * */
  this.deleteByKey = function (target) {
    return new Promise((resolve, reject) => {
      const request = this.db
        .getDBStore(this.config.tableName, ACCESS_MODE.ReadWrite)
        .delete(target)
      request.onsuccess = resolve
      request.onerror = (e) => {
        reject(e.target.error)
      }
    })
  }

  /**
   * @method 根据主键值修改某条数据(主键)
   * @param {Object}
   *   @property {String\|Number} target 目标主键值
   *   @property {Function} handler 处理函数，接收本条数据的引用，对其修改
   *   @property {Function} [success] 修改成功的回调   @return {Object} 返回被修改后的值
   * */
  this.updateByKey = function ({ target, handler }) {
    return new Promise((resolve, reject) => {
      const store = this.db.getDBStore(
        this.config.tableName,
        ACCESS_MODE.ReadWrite
      )
      const request = store.get(target)
      request.onsuccess = (e) => {
        const currentValue = e.target.result
        handler(currentValue)
        const req = store.put(currentValue)
        req.onsuccess = () => resolve(currentValue)
        req.onerror = (e) => {
          reject(e.target.error)
        }
      }
      request.onerror = (e) => {
        reject(e.target.error)
      }
    })
  }

  /**
   * 更新实体数据
   * @param {*} data 实体数据 Object or Array
   * @param {Function} condition 自定义条件筛选函数
   * @param {Function} handler 自定义数据更新函数，在该函数内修改数据，如果该参数未设置，则需要确保更新的数据中包含keyPath的值
   * @param {Number} total 最大更新数量，超过数量停止更新并退出游标遍历
   * @param {Boolean} wait 是否等待更新操作完成
   * @returns 受影响行数
   */
  this.update = function ({
    data,
    condition = null,
    handler = null,
    total = 0,
    wait = false,
  }) {
    if (!data || (isArray(data) && !data.length)) {
      return 0
    }
    const keyPath = this.config.option.keyPath
    data = data || arguments[0]
    condition = condition || arguments[1]
    handler = handler || arguments[2]
    total = total || arguments[3] || 0
    const datas = wrapArray(data)
    const dataObjs = datas.toObject(keyPath)
    if (!condition) {
      condition = (item) => {
        return !!dataObjs[item[keyPath]]
      }
    }
    if (!handler) {
      handler = (item) => {
        Object.assign(item, this.unwrapData(dataObjs[item[keyPath]]))
      }
    }
    return new Promise(async (resolve, reject) => {
      function doReject() {
        hasReject = true
        const err = new Error(
          `update更新操作失败！失败数量：${errors}，具体失败对象参见：errorDatas。`
        )
        err.errorDatas = errors
        reject(err)
      }
      const total = datas.length
      let counter = 0
      let errors = []
      const res = []
      const iterateParam = {
        condition,
        handler: ({ currentValue }) => {
          handler(currentValue)
          res.push(currentValue)
          const req = cursor.update(currentValue)
          if (wait) {
            req.onsuccess = () => {
              counter++
              if (counter >= total) {
                if (!errors.length) {
                  resolve(true)
                } else {
                  doReject()
                }
              }
            }
            req.onerror = () => {
              counter++
              errors.push(d)
              if (counter >= total) {
                doReject()
              }
            }
          }
        },
        over() {
          if (!wait) {
            resolve(res)
          }
        },
        context: createCursorIterateContext(),
      }
      const cursor = await this.db.openDBStoreCursor({ tableName: this.config.tableName })
      if (cursor) {
        iterateCursor(cursor, iterateParam)
      }
    })
  }

  /**
   * 根据主键更新一条数据
   * @param {*} data 包含主键值的数据实体
   * @param {Boolean} wait 是否等待操作完成
   */
  this.updateByKey = function (data, wait = false) {
    if (!data) {
      return 0
    }
    return new Promise((resolve, reject) => {
      const keyPath = this.config.option.keyPath
      const target = data[keyPath]
      const store = this.db.getDBStore({
        tableName: this.config.tableName,
      })
      const getRequest = store.get(target)
      getRequest.onsuccess = (e) => {
        const currentValue = e.target.result
        Object.assign(currentValue, this.unwrapData(data))
        const putRequest = store.put(currentValue)
        if (wait) {
          putRequest.onsuccess = () => {
            resolve(true)
          }
          putRequest.onerror = (e) => {
            reject(e.target.error)
          }
        } else {
          resolve(true)
        }
      }
      getRequest.onerror = (e) => {
        reject(e.target.error)
      }
    })
  }

  /**
   * 根据主键来查询数据
   */
  this.findByKey = function (key) {
    return new Promise((resolve, reject) => {
      try {
        const index = this.db.getDBIndex(
          this.config.tableName,
          this.config.option.keyPath,
          ACCESS_MODE.Readonly
        )
        const erq = index.get(key)
        req.onsuccess = (e) => {
          resolve(this.wrapData(e.target.result))
        }
        req.onerror = (e) => {
          reject(e.target.error)
        }
      } catch (err) {
        reject(err)
      }
    })
  }

  /**
   * 查询所有数据
   */
  this.findAll = function ({ condition = () => true, paging }) {
    return new Promise(async (resolve, reject) => {
      try {
        const resList = []
        const iterateParam = {
          condition,
          handler: (item) => {
            resList.push(item)
          },
          over: () => {
            resolve(resList)
          },
          context: createCursorIterateContext(paging),
        }
        const cursor = await this.db.openDBStoreCursor({
          tableName: this.config.tableName,
          mode: ACCESS_MODE.Readonly,
        })
        if (cursor) {
          iterateCursor(cursor, iterateParam)
        }
      } catch (err) {
        reject(new Error(err.message))
      }
    })
  }

  /**
   * 根据索引条件来查找数据
   * @param {String} indexName 索引名称 String
   * @param {*} target 索引条件 Object or Function
   * @param {Object} paging 分页参数
   */
  this.findByIndex = function ({ indexName, target, condition, paging }) {
    return new Promise(async (resolve, reject) => {
      try {
        if (target && isFunction(target)) {
          target = target()
        }
        if (target === null || target === undefined) {
          return resolve([])
        }
        condition =
          condition ||
          function () {
            return true
          }
        indexName = indexName || this.config.option.keyPath
        const resList = []
        const iterateParam = {
          condition,
          handler: (item) => {
            resList.push(item)
          },
          over: () => {
            resolve(resList)
          },
          context: createCursorIterateContext(paging),
        }
        this.db.openDBIndexCursor({
          tableName: this.config.tableName,
          indexName,
          range: DBKeyRangeOperator.等于(target),
          mode: ACCESS_MODE.Readonly,
          handler: (cursor) => {
            iterateCursor(cursor, iterateParam)
          },
        })
      } catch (err) {
        const errorMsg = `执行findByindex时发生异常：${JSON.stringify({
          tableName: this.config.tableName,
          indexName,
          target,
        })}，${err.message}`
        console.error(errorMsg)
        reject(err)
      }
    })
  }

  /**
   * 按条件统计数据总数
   */
  this.count = function (query) {
    const tableName = this.config.tableName
    return new Promise((resolve, reject) => {
      const store = this.db.createTransaction(tableName, 'readonly')
      const req = store.count(query)
      req.onsuccess = function (evt) {
        resolve(evt.target.result)
      }
      req.onerror = function (evt) {
        console.error(this.error)
        reject(evt.currentTarget.error)
      }
    })
  }

  /**
   * 清空表
   */
  this.clearAll = async function () {
    await this.db.clearTable(this.config.tableName)
  }

  /**
   * 创建一个只读的关联查询属性，自动根据配置参数查询关联数据
   * @param {*} refConfig 关联参数
   */
  this.createRelationProperty = function (modelData, refConfig, caches) {
    if (modelData[refConfig.name]) {
      // 如果对象已存在该属性，则不作处理
      return
    }
    const context = { dbStore, config, Model: this, refConfig, IndexDB }
    if (!refConfig.propertyHandler) {
      const keyPath = this.config.option.keyPath
      let foreignKey = refConfig.foreignKey
      const indexs = refConfig.indexs
      const condition = refConfig.condition
      let getIndexs = null
      let getCondition = null
      if (!foreignKey && !indexs && !condition) {
        // 如果三个均未设置，则默认使用该实体的keyPath索引查找关联数据
        refConfig.foreignKey = foreignKey = keyPath
      }
      if (indexs && indexs.length) {
        getIndexs = (data) => {
          return indexs.map((idx) => {
            const resIdx = { ...idx }
            if (!resIdx.target) {
              resIdx.target = data[keyPath]
            } else if (resIdx.target && isFunction(resIdx.target)) {
              const _target = resIdx.target
              resIdx.target = () => {
                return _target.call(data, data, context)
              }
            }
            if (resIdx.condition) {
              const _condition = resIdx.condition
              resIdx.condition = (item) => {
                return _condition.call(data, item, data, context)
              }
            }
            return resIdx
          })
        }
      }
      if (condition && isFunction(condition)) {
        getCondition = (data) => {
          return (item) => {
            return condition.call(data, item, data, context)
          }
        }
      }
      const refModel = dbStore.models[refConfig.tableName]
      if (!refModel) {
        throw new Error(`实体类型：${refConfig.tableName}不存在！`)
      }
      let propertyHandler = null
      if (foreignKey) {
        // 按指定外键索引查找
        propertyHandler = function (data) {
          return refModel.findByIndex({
            indexName: foreignKey,
            target: data[keyPath],
          })
        }
      } else if (getIndexs) {
        // 按给定的索引查找
        propertyHandler = function (data) {
          return refModel.findByindexs(getIndexs(data))
        }
      } else if (getCondition) {
        // 按给定条件Handler查询数据
        propertyHandler = function (data) {
          return refModel.find(getCondition(data))
        }
      }
      refConfig.propertyHandler = propertyHandler
    }

    Object.defineProperty(modelData, refConfig.name, {
      async get() {
        // 判断当前数据对象的属性缓存，如果存在缓存，则返回缓存数据
        if (refConfig.cache && caches[refConfig.name]) {
          return caches[refConfig.name]
        }
        let res = await refConfig.propertyHandler.call(this, this, context)
        res = refConfig.handler
          ? await refConfig.handler.call(this, res, this, context)
          : res
        if (refConfig.cache && res !== null) {
          caches[refConfig.name] = res
        }
        return res
      },
      set(val) {
        caches[refConfig.name] = val
      },
    })
  }

  /**
   * 创建实体数据的自定义函数
   * @param {*} modelData 实体数据对象
   * @param {*} methods 函数定义对象
   */
  this.createMethods = function (modelData, methods, caches) {
    if (!methods) {
      return modelData
    }
    const Model = this
    Object.keys(methods).forEach((key) => {
      const method = methods[key]
      modelData[key] = function () {
        return method.apply(modelData, [
          ...arguments,
          { dbStore, Model, config, IndexDB, caches },
        ])
      }
    })
  }

  /**
   * 创建实体数据对象的默认函数
   * @param {*} modelData
   * @param {*} caches
   */
  this.createDefaultMethods = function (modelData, caches) {
    const Model = this
    Object.assign(modelData, {
      save() {
        return Model.insert(this)
      },
      remove() {
        return Model.deleteById(this[config.option.keyPath])
      },
      clearPropertyCache(propName) {
        delete caches[propName]
      },
    })
  }

  /**
   * 将指定数据对象包装成实体数据对象
   * @param {*} modelData 实体数据
   */
  this.wrapData = function (modelData) {
    if (!modelData) {
      return
    }
    const datas = wrapArray(modelData)
    datas.forEach((data) => {
      const modalDataCaches = {}
      // 生成关联查询属性
      if (isArray(config.props)) {
        config.props.forEach((refConfig) => {
          this.createRelationProperty(data, refConfig, modalDataCaches)
        })
      }

      if (config.mixnDefaultMethods !== false) {
        this.createDefaultMethods(data, modalDataCaches)
      }
      if (config.methods) {
        this.createMethods(data, config.methods, modalDataCaches)
      }
    })
    return modelData
  }
}
